package cn.org.atool.generator.database.model;

import cn.org.atool.generator.database.config.impl.ColumnConfig;
import cn.org.atool.generator.database.config.impl.GlobalConfig;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.TypeName;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
import org.apache.ibatis.type.TypeHandler;
import org.apache.ibatis.type.UnknownTypeHandler;
import org.mybatis.generator.api.IntrospectedColumn;
import org.mybatis.generator.api.dom.java.FullyQualifiedJavaType;

import java.util.Objects;

import static cn.org.atool.generator.database.model.Naming.underlineToCamel;
import static cn.org.atool.generator.util.ClassNames.CN_Integer;
import static cn.org.atool.generator.util.ClassNames.CN_String;
import static cn.org.atool.generator.util.GeneratorHelper.isBlank;
import static cn.org.atool.generator.util.SchemaKits.NEED_LENGTH_TYPES;
import static cn.org.atool.generator.util.SchemaKits.NEED_SCALE_TYPES;
import static java.util.Optional.ofNullable;

/**
 * 表字段信息
 *
 * @author darui.wu
 */
@SuppressWarnings({"rawtypes", "unused"})
@Getter
@Accessors(chain = true)
public class TableField implements Comparable<TableField> {
    private final String tableName;

    @Getter(AccessLevel.NONE)
    private final GlobalConfig globalConfig;
    /**
     * 字段类别
     */
    @Setter
    private FieldType category = FieldType.Common;
    /**
     * 数据库字段名称
     */
    private final String columnName;
    /**
     * 显式指定Entity字段名称
     */
    @Setter
    private String name;
    /**
     * 显式指定字段java类型
     */
    @Setter
    private TypeName javaType;

    /**
     * 显式指定字段typeHandler
     */
    @Setter
    private Class<? extends TypeHandler> typeHandler;
    /**
     * insert时默认值
     */
    @Setter
    private String insert;
    /**
     * update时默认值
     */
    @Setter
    private String update;
    /**
     * 是否大字段
     */
    @Setter
    private Boolean isLarge;
    /**
     * 字段名称（首字母大写）
     */
    private String capitalName;
    /**
     * 数据库字段类型
     */
    private String jdbcType;
    /**
     * 非空
     */
    private boolean notNull = false;
    /**
     * 默认值
     */
    private String defaults = null;
    /**
     * 字段注释
     */
    private String comment;

    public TableField(GlobalConfig globalConfig, String tableName, String columnName, CustomizedColumn customized) {
        this.globalConfig = globalConfig;
        this.tableName = tableName;
        this.columnName = columnName;

        ColumnConfig config = ColumnConfig.get(tableName, columnName);
        if (config != null) {
            initByConfig(config);
        }
        if (customized == null && globalConfig.getColumnFilter() != null) {
            customized = globalConfig.getColumnFilter().filter(tableName, columnName);
        }
        this.customizedField(customized);
    }


    /**
     * 根据预设的字段设置初始化映射关系
     *
     * @param customized 自定义设置
     */
    private void customizedField(CustomizedColumn customized) {
        if (customized == null) {
            return;
        }
        if (!isBlank(customized.getFieldName())) {
            this.name = customized.getFieldName();
        }
        if (customized.getJavaType() != null) {
            this.setJavaType(customized.getJavaType());
        }
        if (customized.getTypeHandler() != null && !Objects.equals(UnknownTypeHandler.class, customized.getTypeHandler())) {
            this.typeHandler = customized.getTypeHandler();
        }
        if (!customized.isNotLarge()) {
            this.isLarge = false;
        }
        if (!isBlank(customized.getInsert())) {
            this.insert = customized.getInsert();
        }
        if (!isBlank(customized.getUpdate())) {
            this.update = customized.getUpdate();
        }
    }

    private void initByConfig(ColumnConfig config) {
        this.name = config.getField();
        this.insert = config.getInsert();
        this.update = config.getUpdate();
        this.isLarge = config.isLarge();
        this.javaType = config.getJavaType();
        this.jdbcType = ofNullable(config.getJdbcType()).map(Enum::name).orElse(null);
        this.typeHandler = config.getTypeHandler();
        this.category = config.getCategory();
    }

    public void initNamingAndType(String tableName, IntrospectedColumn column) {
        this.jdbcType = column.getActualTypeName();
        if (NEED_LENGTH_TYPES.contains(this.jdbcType.toUpperCase())) {
            this.jdbcType = String.format("%s(%d)", this.jdbcType, column.getLength());
        }
        if (NEED_SCALE_TYPES.contains(this.jdbcType.toLowerCase())) {
            this.jdbcType = String.format("%s(%d, %d)", this.jdbcType, column.getLength(), column.getScale());
        }
        this.initFieldNameIfNeed();
        String capitalName = Naming.capitalFirst(this.removeIsIfNeed(this.name, globalConfig));
        this.initJavaTypeIfNeed(column);
        // is not null
        this.notNull = !column.isNullable();
        this.defaults = column.getDefaultValue();
        if (!isBlank(this.defaults) && this.isStringType()) {
            this.defaults = "'" + this.defaults + "'";
        }
        this.comment = column.getRemarks();
        if (this.comment == null) {
            this.comment = "";
        }
    }

    private boolean isStringType() {
        return Objects.equals(CN_String, this.javaType);
    }

    /**
     * 如果没有预设类型, 从column中获取对应的java类型
     *
     * @param column column元数据定义
     */
    private void initJavaTypeIfNeed(IntrospectedColumn column) {
        if (this.javaType != null) {
            return;
        }
        this.javaType = getJavaType(column);
    }

    private ClassName getJavaType(IntrospectedColumn column) {
        FullyQualifiedJavaType javaType = column.getFullyQualifiedJavaType();
        try {
            if (Objects.equals(javaType.getFullyQualifiedName(), Byte.class.getName())) {
                return CN_Integer;
            }
            return ClassName.get(javaType.getPackageName(), javaType.getShortNameWithoutTypeArguments());
        } catch (Exception e) {
            String err = String.format("getJavaType[%s] of column[%s/%s] error: %s",
                javaType.getFullyQualifiedName(), this.columnName, this.tableName, e.getMessage());
            throw new RuntimeException(err, e);
        }
    }

    /**
     * 如果没有预设类型, 从column中获取对应的java类型
     * <p>
     * 根据字段名称进行驼峰转换
     */
    private void initFieldNameIfNeed() {
        // 如果字段名称没有预设
        if (!isBlank(this.name)) {
            return;
        }
        Naming naming = globalConfig.getColumnNaming();
        if (naming == Naming.underline_to_camel) {
            this.name = underlineToCamel(this.columnName);
        } else {
            this.name = this.columnName;
        }
    }

    /**
     * Boolean类型is前缀处理
     *
     * @param input        字段名称
     * @param globalConfig GlobalConfig
     * @return ignore
     */
    private String removeIsIfNeed(String input, GlobalConfig globalConfig) {
        if (globalConfig.needRemoveIsPrefix(input, this.getJavaType())) {
            return input.substring(2);
        } else {
            return input;
        }
    }

    /**
     * 是否主键
     *
     * @return ignore
     */
    public boolean isPrimary() {
        return this.category == FieldType.PrimaryKey || this.category == FieldType.PrimaryId;
    }

    /**
     * 是否自增主键
     *
     * @return ignore
     */
    public boolean isPrimaryId() {
        return this.category == FieldType.PrimaryId;
    }

    /**
     * 是否gmt字段
     *
     * @return ignore
     */
    public boolean isGmt() {
        return this.category == FieldType.GmtCreate || this.category == FieldType.GmtModified;
    }

    /**
     * 是否逻辑删除字段
     *
     * @return ignore
     */
    public boolean isDeleted() {
        return this.category == FieldType.IsDeleted;
    }

    /**
     * 按字母排序
     *
     * @param field TableField
     * @return 排序
     */
    @Override
    public int compareTo(TableField field) {
        if (field == null) {
            return 1;
        }
        int order = this.category.compareTo(field.category);
        if (order == 0) {
            order = this.name.compareTo(field.name);
        }
        return order;
    }
}